package com.core.snowflake.service;


import com.core.data.redis.RedisUtil;
import com.core.snowflake.comm.AES;
import com.core.snowflake.config.SnowConst;
import com.core.snowflake.config.SnowFlakeProperties;
import com.core.snowflake.dto.SnowFlakeSyncDataDto;
import com.core.snowflake.core.SnowFlakeUtil;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.client.RestTemplate;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class SnowFlakeService {
    Logger log = LoggerFactory.getLogger("snowFlakeUnit");
    ScheduledExecutorService worker = new ScheduledThreadPoolExecutor(1);
    public SnowFlakeUtil snowFlakeUtil;


   // @Autowired(required = false)
   // RedisUtil redisUtil;
    @Autowired(required = false)
    @Qualifier(value = "sn_restTemplate")
    public RestTemplate restTemplate;

    @Autowired(required = false)
    RedisUtil redisUtil;

    SnowFlakeProperties snowFlakeProperties;


    public SnowFlakeService(SnowFlakeUtil _snowFlakeUtil, SnowFlakeProperties _snowFlakeProperties) {
        this.snowFlakeUtil = _snowFlakeUtil;
        this.snowFlakeProperties = _snowFlakeProperties;
        //-----------------------------------------------
        worker.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (doSync()){}
                else {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    doInt(12,10000);
                }
            }
        }, 10, 60 * 7, TimeUnit.SECONDS);//每？秒获取接口短讯状态一次
        //-----------------------------------------------
/*
        //第一次初始化延迟15秒次发起对数据中心同步请求
        worker.schedule(new Runnable() {
            @Override
            public void run() {
                doInt(3,5000);
            }
        }, 10, TimeUnit.SECONDS);
*/
        /*if (_snowFlakeProperties.getType().equals("datacenter")) {

            if (_snowFlakeProperties.isIsselfdatacenter()) {
                //如果自己就是数据中心模式，延迟15秒执行一次,等待contoller生成
                worker.schedule(new Runnable() {
                    @Override
                    public void run() {
                        doSync();
                    }
                }, 50, TimeUnit.SECONDS);
            } else {
                //如果获取ID模式，立马执行一次
                worker.schedule(new Runnable() {
                    @Override
                    public void run() {
                        doSync();
                    }
                }, 7, TimeUnit.SECONDS);
            }

        }
*/
    }

    /**
     * 初始化线程
     * 连续3次循环获取和数据中心进行数据同步
     */
    public void doInt(int loops,int waits) {

        for (int j = 0; j < loops; j++) {
            if (doSync())
                break;
            else {
                try {
                    Thread.sleep(waits);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 和数据中心进行同步
     *
     * @return
     */
    public boolean doSync() {
        boolean isdone = false;

        try {
            String key=SnowConst.secret +System.currentTimeMillis();
            String token= AES.encrypt("chris",SnowConst.secret);
            token= URLEncoder.encode(token,"utf-8");
            //-----------------从数据中心取数据------------
            //String tmp = AES.decrypt(token,SnowConst._salt,"AES");
            ObjectMapper om = new ObjectMapper();
            String host = snowFlakeProperties.getHost();
            //去掉最后 /
            if (host.lastIndexOf("/")==host.length()-1)
                host=host.substring(0,host.length()-1);
            String json ="";
            SnowFlakeSyncDataDto dto = null;
            if (snowFlakeProperties.getType().equals("datacenter")) {
                //todo 理论上应该所有参数加密在token里面，目前为了方便观测，和调用，仅仅用token验证是否有效，并未对参数进行加密
                //datacenter模式
                 json = restTemplate.getForObject(host + "?synctag=" + snowFlakeUtil.getSyncTag() + "&tag=" + snowFlakeProperties.getTag() + "&ns=" + snowFlakeProperties.getNs() + "&d=" + snowFlakeUtil.getDatacenterId() + "&w=" + snowFlakeUtil.getWorkerId() + "&token=" + token, String.class);
                //log.info("雪花中心get "+host+ "?synctag=" + snowFlakeUtil.getSyncTag() + "&tag=" + snowFlakeProperties.getTag() + "&ns=" + snowFlakeProperties.getNs()+ "&d=" + snowFlakeUtil.getDatacenterId()+ "&w=" + snowFlakeUtil.getWorkerId());

                //忽略未知的属性，导致所有的故障
                om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                try {
                    dto = om.readValue(json, SnowFlakeSyncDataDto.class);
                } catch (Exception e) {
                    log.error("雪花ID中心：未能从[数据中心]同步数据 " +host+ "?synctag=" + snowFlakeUtil.getSyncTag() + "&tag=" + snowFlakeProperties.getTag()+ "&ns=" + snowFlakeProperties.getNs()+ "&d=" + snowFlakeUtil.getDatacenterId()+ "&w=" + snowFlakeUtil.getWorkerId()+"&token="+token+" e"+ e.toString());
                }
            }
            else
            {
                try {
                    //redis直读模式(要保证ca-redis和业务redis是同一个数据库)
                    dto = syncredis(redisUtil,snowFlakeUtil.getSyncTag(), snowFlakeProperties.getTag(), snowFlakeProperties.getNs(), snowFlakeUtil.getDatacenterId(), snowFlakeUtil.getWorkerId(), token);
                }
                catch (Exception e) {
                    log.error("雪花ID中心：未能从[redis]同步数据 " +host+ "?synctag=" + snowFlakeUtil.getSyncTag() + "&tag=" + snowFlakeProperties.getTag()+ "&ns=" + snowFlakeProperties.getNs()+ "&d=" + snowFlakeUtil.getDatacenterId()+ "&w=" + snowFlakeUtil.getWorkerId()+"&token="+token+" e"+ e.toString());
                }
            }

            if (dto == null) {
                //log.error("雪花算法：未能从数据中心同步数据");
                //出错不抛出，下次继续获取,直到获取成功
                // throw new IllegalArgumentException("雪花算法：从数据中心获取数据出错");
            } else {
                snowFlakeUtil.syncSnowFlake(dto);
                log.info("雪花ID中心从["+snowFlakeProperties.getType()+"]同步成功 "+dto.getSyncTag()+" ns:"+dto.getNs()+" tag:"+dto.getTag()+" d:"+dto.getDatacenterId()+" w:"+dto.getWorkerId());
                isdone = true;
            }
            //------------------------------------------
        } catch (Exception ex) {
            log.error("雪花ID中心：未能从["+snowFlakeProperties.getType()+"]同步数据， e： " +snowFlakeProperties.getHost()+ "?synctag=" + snowFlakeUtil.getSyncTag() + "&tag=" + snowFlakeProperties.getTag()+ "&ns=" + snowFlakeProperties.getNs()+ "&d=" + snowFlakeUtil.getDatacenterId()+ "&w=" + snowFlakeUtil.getWorkerId()+" e:"+ex.toString());
        }
        return isdone;
    }

    /**
     * 计算雪花ID
     * todo 理论上应该所有参数加密在token里面，目前为了方便观测，和调用，仅仅用token验证是否有效，并未对参数进行加密
     * @param redisUtil redis对象
     * @param synctag 客户端标签(唯一标识)目前暂时用来记录IP，并无业务逻辑限制
     * @param tag tg
     * @param ns 命名空间
     * @param d 数据ID
     * @param w 机器ID
     * @param token
     * @return
     * @throws Exception
     */
    public static SnowFlakeSyncDataDto syncredis(RedisUtil redisUtil,String synctag, String tag,  String ns,long d, long w, String token) throws Exception {
        if (synctag == null || synctag.equals(""))
            throw new Exception("synctag不能为空");

        if (redisUtil == null)
            throw new Exception( "redisUtil未初始化");

        if (token==null)
            throw new Exception( "需要token");
        //后门！！！
        if (!token.equals("chrishusan")) {
            token = URLDecoder.decode(token, "utf-8");
            String decrypt = AES.decrypt(token, SnowConst.secret);
            if (decrypt == null && decrypt.indexOf("chris") != 0)
                throw new Exception("token错误");
        }
        boolean issync = false;
        //(1)查找是否存在未过期数据先判断是否存在
        SnowFlakeSyncDataDto dto = null;
        dto = redisUtil.get(String.format(SnowConst.Sf, ns, d, w), SnowFlakeSyncDataDto.class);
        //是否为空 && 是否是同一个 synctag  && 是否过期时间剩余时间大于 15 分钟(小于剩余时间，就当作新增，错位时间，留给判断过期）
        if (dto != null && dto.getSyncTag().equals(synctag) && (dto.getLastSyncTime()+dto.getSyncMaxTime()) > (System.currentTimeMillis()+900000) ) {
            //存在,更新过期时间，并返回
            dto.setLastSyncTime(System.currentTimeMillis());
            dto.setSyncMaxTime(SnowConst.SfExpiresTime);
            if (tag != null && !tag.equals(""))
                dto.setTag(tag);
            //标识同步
            dto.setCodeState("synchronization");
            issync = redisUtil.setIfPresent(String.format(SnowConst.Sf, ns, d, w), dto, SnowConst.SfExpiresTime / 1000);

        }
        if (issync) {
            return dto;
        } else {
            dto = new SnowFlakeSyncDataDto();
            dto.setNs(ns);
            //标识新增
            dto.setCodeState("add");
            dto.setSyncTag(synctag);
            dto.setSyncMaxTime(SnowConst.SfExpiresTime);
            if (tag != null && !tag.equals(""))
                dto.setTag(tag);
            //不存在,轮询现有 ns + d + w , 找出空
            for (long i = 0; i <= 31; i++) {
                for (long j = 0; j <= 31; j++) {
                    //dto = redisUtil.get(String.format(DataCenterConst.Sf, ns, i, j), SnowFlakeSyncDataDto.class);
                    // if (dto == null) {
                    //插入新记录。并返回
                    dto.setLastSyncTime(System.currentTimeMillis());
                    dto.setDatacenterId(i);
                    dto.setWorkerId(j);
                    //插入不存在的记录是否成功
                    if( redisUtil.setIfAbsent(String.format(SnowConst.Sf, ns, i, j), dto, SnowConst.SfExpiresTime / 1000))
                    {
                        return  dto;
                    }

                    //  }
                }

            }
        }
        return null;
    }


}
